Pinvon's Blog

所见, 所闻, 所思, 所想

装饰器和方法

装饰器

装饰器的本质: 本质上是一个函数, 以其他的函数作为参数并修改, 最后将修改后的函数替换原有函数.

如:

def identity(f):
    return f
@identity
def foo():
    return 'bar'

# 等价于
def foo():
    return 'bar'
foo = identity(foo)

应用场景

如果多个函数在调用前后都要执行同一段代码, 就可以把这个通用的代码分离出来, 以装饰器的方式来修改其他函数. 如:

class  Store(object):
    def get_food(self, username, food):
        if username != 'admin':
            raise Exception("This user is not allowed to get food")
        return self.storage.get(food)

    def put_food(self, username, food):
        if username != 'admin':
            raise Exception("This user is not allowed to put food")
        self.storage.put(food)
  1. 分离出通用代码, 即其中的 if 语句.
def check_is_admin(username):
    if username != 'admin':
        raise Exception("This user is not allowed to get food")

class Store(object):
    def get_food(self, username, food):
        check_is_admin(username)
        return self.storage.get(food)

    def put_food(self, username, food):
        check_is_admin(username)
        self.storage.put(food)
  1. 修改成装饰器:
def check_is_admin(f):
    def wraper(*args, **kwargs):
        if kwargs.get('username') != 'admin':
            raise Exception("This user is not allowed to get food")
        return f(*args, **kwargs)
    return wrapper

class Store(object):
    @check_is_admin
    def get_food(self, username, food):
        return self.storage.get(food)

    @check_is_admin
    def put_food(self, username, food):
        self.storage.put(food)

如前所述, 装饰器会用一个动态创建的新函数代替原来的函数, 但是新函数的问题在于, 缺少了很多原来函数的属性, 如文档字符串和名字.

Python 内置的 functools 模块提供了 wraps 装饰器, 将这些属性复制给我们的装饰器. 最终代码如下:

import functools


def check_is_admin(f):
    @functools.wraps(f)
    def wraper(*args, **kwargs):
        if kwargs.get('username') != 'admin':
            raise Exception("This user is not allowed to get food")
        return f(*args, **kwargs)
    return wrapper

class Store(object):
    @check_is_admin
    def get_food(self, username, food):
        return self.storage.get(food)

    @check_is_admin
    def put_food(self, username, food):
        self.storage.put(food)

方法

方法是指类内部的函数.

实例方法

Python 会自动将对象传给方法的 self 参数; 如:

class Foo(object):
    def get_size(self):
        ...

# 调用方法
foo = Foo()
foo.get_size()

静态方法

静态方法: 属于类, 但不依赖于实例.

class Pizza(object):
    @staticmethod
    def mix_ingredients(x, y):
        return x + y

    def cook(self):
        return self.mix_ingredients(self.cheese, self.vegetables)

使用 @staticmethod 可以声明一个方法是静态方法.

调用方式:

pizza = Pizza()
pizza.mix_ingredients()
# 或
Pizza.mix_ingredients()

类方法

类方法使用 @classmethod 声明, 第一个参数为 cls, 调用方式和静态方法一样.

class Pizza(object):
    radius = 42
    @classmethod
    def get_radius(cls):
        return cls.radius

抽象方法

抽象方法是定义在基类中, 可能有或者没有任何实现的方法. 最简单的抽象方法如下:

class Pizza(object):
    @staticmethod
    def get_radius():
        raise NotImplementedError

任何继承自 Pizza 的子类都要实现并重写 get_radius(), 否则调用这个方法会引发异常.

super()

super() 用来调用父类的方法.

class A:
    def add(self, x):
        y = x + 1
        print(y)

class B(A):
    def add(self, x):
        super().add(x)

b = B()
b.add(2)  # 3

小结

classmethod 主要用途是作为构造函数. Python 只有一个构造函数 __new__(), 并且 __new__() 比较复杂, 我们一般不会去重写该方法, 由系统自动生成即可. 因此需要使用其他构造函数(classmethod)来满足我们的需求, 故 classmethod 的最后一句一般是 return cls(xxx).

class DateTest(object):

    def __init__(self, year=None, month=None, day=None):
        self.year = year
        self.month = month
        self.day = day

    def out_date(self):
        print("Year:", self.year)
        print("Month:", self.month)
        print("Day:", self.day)

    @classmethod
    def string_date(cls, date):
        year, month, day = map(int, date.split("-"))
        date_res = cls(year, month, day)
        return date_res

t = DateTest.string_date("2017-06-21")
t.out_date()

staticmethod 的主要作用是限定 namespace, 虽然是个普通的方法, 但它只有这个 class 会用到, 不适合作为模块级的方法.

Comments

使用 Disqus 评论
comments powered by Disqus